package manager

import (
	"fmt"
	"gitverse.ru/synapse/kubelatte/pkg/logs"
	"gitverse.ru/synapse/kubelatte/pkg/operator/controllers"
	"gitverse.ru/synapse/kubelatte/pkg/util"
	"golang.org/x/net/context"
	v1 "k8s.io/api/authorization/v1"
	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
	kclientset "k8s.io/client-go/kubernetes"
	"sync"
	"time"
)

var PermissionsSet bool

type PermissionManager struct {
	startOnly   bool
	logOnly     bool
	checkPeriod time.Duration
	ticker      *time.Ticker
	permissons  []permission
	client      kclientset.Interface
}

func InitPermissionManager(client kclientset.Interface) *PermissionManager {
	if !util.IsPermissionsCheck() {
		PermissionsSet = true
		return nil
	}

	return &PermissionManager{
		checkPeriod: util.IKbltPermissionsCheckPeriod,
		logOnly:     util.IsKbltPermissionsLogOnly(),
		startOnly:   util.IsKbltPermissionsStartOnly(),
		client:      client,
	}
}

func (p *PermissionManager) StartCheck() {
	if p == nil {
		return
	}

	p.checkPermsForFirstTime(context.Background())
	go p.startPermissionWatcher(context.Background())
}

func (p *PermissionManager) checkPermsForFirstTime(ctx context.Context) {
	for {
		err := p.checkPermissionsAndSetStatus(ctx)
		if err == nil {
			break
		}
		time.Sleep(p.checkPeriod)
	}
}

func (p *PermissionManager) checkPermissionsAndSetStatus(ctx context.Context) error {
	err := p.checkPermissions(ctx)
	PermissionsSet = p.logOnly || err == nil
	if p.logOnly {
		return nil
	}
	return err
}

func (p *PermissionManager) startPermissionWatcher(ctx context.Context) {
	if p.startOnly {
		return
	}
	p.ticker = time.NewTicker(p.checkPeriod)
	_ = p.checkPermissionsAndSetStatus(ctx)
	for {
		select {
		case <-p.ticker.C:
			_ = p.checkPermissionsAndSetStatus(ctx)
		case <-ctx.Done():
			p.stop(ctx)
			return
		}
	}
}

func (p *PermissionManager) stop(ctx context.Context) {
	if p.ticker != nil {
		p.ticker.Stop()
	}
	ctx.Done()
}

func (p *PermissionManager) checkPermissions(ctx context.Context) error {
	if p.permissons == nil {
		p.permissons = p.getPermissions()
	}
	var requestedPermissions = p.permissons
	var cnt int
	for _, perm := range requestedPermissions {
		cnt += len(perm.Verbs) * len(perm.Resources)
	}
	errChan := make(chan error, cnt)
	wg := &sync.WaitGroup{}

	for _, permission := range requestedPermissions {
		for _, verb := range permission.Verbs {
			for _, resource := range permission.Resources {
				wg.Add(1)
				time.Sleep(time.Millisecond * 100) // do not dos kuber api
				go func(verb, resource, group, version, namespace string, waitGroup *sync.WaitGroup, errChan chan error) {
					defer waitGroup.Done()
					rv := &v1.SelfSubjectAccessReview{
						Spec: v1.SelfSubjectAccessReviewSpec{
							ResourceAttributes: &v1.ResourceAttributes{
								Namespace: namespace,
								Verb:      verb,
								Group:     group,
								Version:   version,
								Resource:  resource,
							},
						},
					}
					resp, err := p.client.AuthorizationV1().SelfSubjectAccessReviews().Create(ctx, rv, metav1.CreateOptions{})
					if err != nil {
						errChan <- err
						logs.Errorf("cant check permissions: %s", err)
						return
					}
					if !resp.Status.Allowed {
						errChan <- fmt.Errorf("not allowed")
						logs.Errorf("[Permission Manager] not allowed %s %s %s %v %s", verb, resource, resp.Status.Reason, resp.Status.Denied, resp.Status.EvaluationError)
						return
					}
				}(verb, resource, permission.Group, permission.Version, permission.Namespace, wg, errChan)
			}
		}
	}
	wg.Wait()
	close(errChan)
	if err := <-errChan; err != nil {
		return fmt.Errorf("[Init Manager] Permissions error")
	}

	return nil
}

func (p *PermissionManager) getPermissions() []permission {
	var requestedPermissions []permission

	var namespaces = permission{
		Verbs:     []string{"get", "list", "watch"},
		Group:     "",
		Resources: []string{"namespaces"},
		Namespace: "",
	}
	var leases = permission{
		Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
		Group:     "coordination.k8s.io",
		Resources: []string{"leases"},
		Namespace: controllers.OperatorNamespace,
	}

	var latteStatuses = []string{"scopes/status", "templates/status"}
	var latteRes = []string{"scopes", "templates"}

	if controllers.IsMutatorRole() {
		latteRes = append(latteRes, "triggers")
		latteStatuses = append(latteStatuses, "triggers/status")
	}
	if controllers.IsCreatorRole() {
		requestedPermissions = append(requestedPermissions,
			permission{
				Verbs:     []string{"get", "list", "watch"},
				Group:     "kubelatte.io",
				Resources: []string{"triggerinstances"},
				Namespace: "",
			},
			permission{
				Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
				Group:     "kubelatte.io",
				Resources: []string{"triggerinstances/status"},
				Namespace: "",
			})
	}

	if controllers.IsSideEffectRole() {
		requestedPermissions = append(requestedPermissions,
			permission{
				Verbs:     []string{"create"},
				Group:     "kubelatte.io",
				Resources: []string{"triggerinstances"},
				Namespace: "",
			},
		)
	}

	var lateCrdPem = permission{
		Verbs:     []string{"get", "list", "watch"},
		Group:     "kubelatte.io",
		Resources: latteRes,
		Namespace: controllers.OperatorNamespace,
	}
	var statuses = permission{
		Verbs:     []string{"get", "list", "watch", "create", "update", "watch", "delete", "patch"},
		Group:     "kubelatte.io",
		Resources: latteStatuses,
		Namespace: controllers.OperatorNamespace,
	}

	requestedPermissions = append(requestedPermissions, statuses, leases, lateCrdPem, namespaces)
	return requestedPermissions
}

type permission struct {
	Verbs     []string
	Group     string
	Resources []string
	Version   string
	Namespace string
}
